import serial
import json
import time
from typing import Optional, Dict, Any, Tuple
from enum import Enum
from serial.tools import list_ports
import threading


class ResponseStatus(Enum):
    SUCCESS = 1  # 成功
    TIMEOUT = 2  # 超时
    INVALID_FORMAT = 3  # 格式不对
    COMMAND_MISMATCH = 4  # 匹配失败
    SERIAL_ERROR = 5  # 错误


class ESP32PackagerController:
    def __init__(self, port: str = None, baudrate: int = 115200,
                 timeout: float = 2.0, debug: bool = True):
        self.port = port
        self.baudrate = baudrate
        self.timeout = timeout
        self.debug = debug
        self.ser = None

    def __enter__(self):
            if self.connect():
                return self
            raise serial.SerialException("无法打开串口")

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

    def get_available_ports(self) -> list:
        """获取所有可用串口列表"""
        return [port.device for port in list_ports.comports()]

    def auto_detect_port(self, expected_id: str = "ESP32_dabao") -> Optional[str]:
        """
        自动检测设备所在的串口
        :param expected_id: 预期的设备ID
        :return: 成功返回串口路径，失败返回None
        """
        for port in self.get_available_ports():
            if self.debug:
                print(f"正在检测串口: {port}")

            try:
                # 使用临时控制器进行检测
                with ESP32PackagerController(
                        port=port,
                        baudrate=self.baudrate,
                        timeout=2,  # 更短的检测超时
                        debug=self.debug
                ) as detector:
                    status, response = detector.send_command("get_device_ID", max_retries=1)

                    if status == ResponseStatus.SUCCESS and response.get("deviceID") == expected_id:
                        if self.debug:
                            print(f"√ 找到目标设备在 {port}")
                        self.port = port
            except Exception as e:
                if self.debug:
                    print(f"  {port} 检测失败: {str(e)}")
                continue
        return None

    def connect(self) -> bool:
        """建立串口连接"""
        if not self.port:
            if self.debug:
                print("× 未指定串口端口")
            return False

        try:
            self.ser = serial.Serial(
                port=self.port,
                baudrate=self.baudrate,
                timeout=self.timeout
            )
            time.sleep(1)  # 缩短初始化等待时间
            if self.debug:
                print(f"√ 已连接 {self.port}")
            return True
        except serial.SerialException as e:
            if self.debug:
                print(f"× 连接失败: {str(e)}")
            return False

    def close(self):
        """关闭串口连接"""
        if self.ser and self.ser.is_open:
            self.ser.close()
            if self.debug:
                print(f"√ 已关闭 {self.port} 连接")

    def _log_communication(self, direction: str, message: str):
        """记录通信日志"""
        if self.debug:
            timestamp = time.strftime("%H:%M:%S", time.localtime())
            millis = f"{int(time.time() % 1 * 1000):03d}"
            print(f"[{direction}][{timestamp}.{millis}] {message}")

    def _validate_response(self, sent_command: str, response: Dict) -> Tuple[ResponseStatus, Optional[Dict]]:
        """验证响应格式"""
        if not response:
            return ResponseStatus.TIMEOUT, None

        if "command" not in response or "status" not in response:
            return ResponseStatus.INVALID_FORMAT, None

        if response["command"] != sent_command:
            return ResponseStatus.COMMAND_MISMATCH, None

        return ResponseStatus.SUCCESS, response

    def send_command(self, command: str, max_retries: int = 3) -> Tuple[ResponseStatus, Optional[Dict]]:
        """
        发送命令并获取验证后的响应
        :param command: 要发送的命令
        :param max_retries: 最大重试次数
        :return: (状态码, 解析后的响应数据)
        """
        if not self.ser or not self.ser.is_open:
            return ResponseStatus.SERIAL_ERROR, None

        for attempt in range(max_retries):
            try:
                # 清空输入缓冲区，避免残留数据影响
                self.ser.reset_input_buffer()

                # 发送命令
                self._log_communication("Tx", command)
                self.ser.write(f"{command}\n".encode('utf-8'))

                # 读取响应
                start_time = time.time()
                response_raw = ""
                while time.time() - start_time < self.timeout:
                    if self.ser.in_waiting:
                        line = self.ser.readline().decode('utf-8', errors='ignore').strip()
                        if line:
                            response_raw = line
                            self._log_communication("Rx", response_raw)
                            break  # 读取到有效数据后退出循环
                    else:
                        # 避免忙等待，适当释放CPU
                        time.sleep(0.001)
                else:
                    # 超时后进入下一次重试
                    continue

                # 尝试解析响应
                try:
                    response = json.loads(response_raw)
                    status, valid_response = self._validate_response(command, response)
                    if status == ResponseStatus.SUCCESS:
                        return status, valid_response
                    # 验证失败但数据格式正确，直接返回错误状态
                    return status, None
                except json.JSONDecodeError:
                    return ResponseStatus.INVALID_FORMAT, None

            except serial.SerialException as e:
                print(f"[ERROR] Attempt {attempt + 1} failed: {e}")
                time.sleep(0.5 * (attempt + 1))  # 指数退避策略
                continue

        return ResponseStatus.SERIAL_ERROR, None

    def trigger_pack(self) -> Tuple[bool, Optional[Dict]]:
        """
        触发打包动作
        :return: (是否成功, 完整响应)
        """
        status, response = self.send_command("trigger_pack")  # 注意修正拼写
        success = status == ResponseStatus.SUCCESS and response.get("status") == "ok"
        return success, response

    def get_status(self) -> Tuple[Optional[Dict], ResponseStatus]:
        """
        获取当前所有输入引脚状态
        :return: (状态数据, 状态码)
        """
        status, response = self.send_command("get_status")
        if status == ResponseStatus.SUCCESS:
            return response.get("status"), status
        return None, status


if __name__ == "__main__":
    # 使用示例
    controller = ESP32PackagerController()

    # 自动检测设备
    controller.auto_detect_port()

    if controller.connect():
        # 测试设备ID
        status, response = controller.send_command("get_device_ID")
        if status == ResponseStatus.SUCCESS:
            print(f"设备信息: {response}")

        # 触发打包测试
        success, response = controller.trigger_pack()
        print(f"打包结果: {'成功' if success else '失败'} {response}")

        # 获取状态
        status_data, status_code = controller.get_status()
        if status_code == ResponseStatus.SUCCESS:
            print("当前状态:")
            for pin, value in status_data.items():
                print(f"  {pin}: {'HIGH' if value else 'LOW'}")
        else:
            print(f"状态获取失败: {status_code.name}")
    else:
        print("× 连接失败")